#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (C) 2009 Matteo Bertini

import os
import sys
import time
import hashlib


# utils

def mkdir(file):
    """
    Create the path to the `file` and returns `file`
    Usage:
        filename = mkdir(filename)
        open(filename, 'w+').write(data)
    """
    dir = os.path.dirname(file)
    if dir and not os.path.exists(dir):
        os.makedirs(dir)
    return file

def key(*args, **kwargs):
    """
    Create an hash key from args
    """
    k = hashlib.md5(str(args)+str(kwargs)).hexdigest()
    return k[:2]+'/'+k

# deps management

def rebuild(root, depth=0, yielded=None):
    """
    Traverse the deps tree down from the root,
    prepend the deps depth (dependency groups):
        yields (deps, inode)
    """
    node = root[0]
    deps = root[1]
    yielded = yielded or set()
    for dep in deps:
        for inode in rebuild(dep, depth+1, yielded):
            yielded.add(inode[1])
            yield inode
    if node not in yielded:
        yield (depth, node)

def changed(node, updated=None):
    """
    Scan for changed deps:
        returns (node, [deps])
                 or
                True
    """
    # TODO: make code understandable and move to uniform output
    status = node.mtime()
    updated = updated or status
    last_updated = max([updated, status])
    if node.deps:
        deps = [changed(dep, last_updated) for dep in node.deps]
        deps = [d for d in deps if d]
        if deps:
            return (node, [d for d in deps if d != True])
    if updated < status:
        #print 'CHANGED:', node
        return True
    #print 'up to date:', node
    if status == -1:
        return (node, [])

def chunk_by_priority(prioritylist):
    """
    Returns a list of lists splitting on priority change:
    >>> list(chunk_by_priority([(1, 'a'), (2, 'c'), (1, 'b')]))
    [['a'], ['c'], ['b']]

    Sort the input for the minimal split:
    >>> list(chunk_by_priority([(1, 'a'), (1, 'b'), (2, 'c')]))
    [['a', 'b'], ['c']]
    """
    chunk = []
    last_p = None
    for p, e in prioritylist:
        if last_p == None:
            last_p = p
        if last_p == p:
            chunk += [e]
        else:
            yield chunk
            chunk = [e]
            last_p = p
    yield chunk

def node_run(node):
    try:
        node._run()
    except:
        print "Error making", node
        if os.path.exists(node.name):
            try:
                print "Removing incomplete {0!r}".format(node.name)
                os.remove(node.name)
            except:
                print "Error removing {0!r}".format(node.name)
        raise
    node.touch()

class Node(object):
    def __init__(self, name, deps=None):
        self.name = name
        self.deps = deps or []
    def mtime(self):
        if os.path.exists(self.name):
            return os.stat(self.name).st_mtime
        else:
            return -1
    def touch(self):
        if not os.path.exists(self.name):
            raise ValueError("_run() generated no file named {0!r}".format(self.name))
    def changed(self):
        """
        Returns the priority list of out-of-date deps
        """
        tree = changed(self)
        if tree:
            return sorted(rebuild(tree), reverse=True)
        else:
            return []
    def load(self, debug=True):
        changed = self.changed()
        if len(changed):
            print "Rebuilding {0} deps...".format(len(changed))
        for p, n in changed:
            try:
                if debug:
                    print "DEBUG: Building", repr(n)
                node_run(n)
            except:
                print "Problem processing {0!r} dep: {1!r}".format(self, n)
                raise
        if debug:
            print "DEBUG: Loading", repr(self)
        return self._load()
    def _run(self):
        pass
    def _load(self):
        pass
    def __str__(self):
        return self.name
    def __repr__(self):
        return "<{0} {1!r}>".format(self.__class__.__name__, self.name)


if __name__ == "__main__" and "__file__" in globals():
    import sys
    args = sys.argv[1:]
    if "eval" in args[:1]:
        eval(" ".join(args[1:]))
    else:
        print "Running %r doctests..." % __file__
        import doctest
        doctest.testmod()
        print "DONE!"

